// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "thread.h"
#include <stdio.h>
#include <iostream>
#ifdef CHANGED
    #include "kernellocks.h"
	#include "process.h"
#endif

using namespace std;

KernelLocks _kernelLocks;


void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len+1];	// Kernel buffer to put the name in

    if (!buf) return;

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Create\n");
	delete buf;
	return;
    }

    buf[len]='\0';

    fileSystem->Create(buf,0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len+1];	// Kernel buffer to put the name in
    OpenFile *f;			// The new open file
    int id;				// The openfile id

    if (!buf) {
	printf("%s","Can't allocate kernel buffer in Open\n");
	return -1;
    }

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Open\n");
	delete[] buf;
	return -1;
    }

    buf[len]='\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if ( f ) {
	if ((id = currentThread->space->fileTable.Put(f)) == -1 )
	    delete f;
	return id;
    }
    else
	return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.
    
    char *buf;		// Kernel buffer for output
    OpenFile *f;	// Open file for output

    if ( id == ConsoleInput) return;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer for write!\n");
	return;
    } else {
        if ( copyin(vaddr,len,buf) == -1 ) {
	    printf("%s","Bad pointer passed to to write: data not written\n");
	    delete[] buf;
	    return;
	}
    }

    if ( id == ConsoleOutput) {
      for (int ii=0; ii<len; ii++) {
	printf("%c",buf[ii]);
      }

    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    f->Write(buf, len);
	} else {
	    printf("%s","Bad OpenFileId passed to Write\n");
	    len = -1;
	}
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf;		// Kernel buffer for input
    OpenFile *f;	// Open file for output

    if ( id == ConsoleOutput) return -1;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer in Read\n");
	return -1;
    }

    if ( id == ConsoleInput) {
      //Reading from the keyboard
      scanf("%s", buf);

      if ( copyout(vaddr, len, buf) == -1 ) {
	printf("%s","Bad pointer passed to Read: data not copied\n");
      }
    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    len = f->Read(buf, len);
	    if ( len > 0 ) {
	        //Read something from the file. Put into user's address space
  	        if ( copyout(vaddr, len, buf) == -1 ) {
		    printf("%s","Bad pointer passed to Read: data not copied\n");
		}
	    }
	} else {
	    printf("%s","Bad OpenFileId passed to Read\n");
	    len = -1;
	}
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if ( f ) {
      delete f;
    } else {
      printf("%s","Tried to close an unopen file\n");
    }
}

//#ifdef CHANGED
void Kernel_Func(unsigned int funcVAddr)
{
	printf("[KERNEL PID#%i, TID#%i] Thread %s from address %i Started\n", currentThread->getProcessID(), currentThread->getThreadID(),currentThread->getName(), funcVAddr);
	machine->WriteRegister(PCReg,funcVAddr);
	machine->WriteRegister(NextPCReg,funcVAddr+4);
	machine->Run();
	printf("[KERNEL PID#%i, TID#%i] Thread %s Finished\n",currentThread->getProcessID(), currentThread->getThreadID(), currentThread->getName());
}

int Fork_Syscall(unsigned int nameVAddr, int len, unsigned int funcVAddr) {
	char* name;               //Expressive name of thread
	int threadID;             //Identifier of the new threads. Unique per process
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Fork syscall started.\n",currentThread->getProcessID(), currentThread->getThreadID());
	//Generate a name for this thread
	name = new char[len+1];
	if (!name) {
		printf("%s","Can't allocate kernel buffer in Fork\n");
		return -1;
	}
	if( copyin(nameVAddr,len,name) == -1 ) {
		printf("%s","Bad pointer passed to Fork name\n");
		delete[] name;
		return -1;
	}
	name[len]='\0';
	ProcessEntry* currentProcess = processTable.getProcess(currentThread->getProcessID());
	if(currentProcess == NULL){
		printf("%s","Could not find the current process in process table\n");
		delete[] name;
		return -1;
	}
	threadID = currentProcess->Fork(name, funcVAddr);
	delete []name;
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Fork syscall Finished.\n",currentThread->getProcessID(), currentThread->getThreadID());
	return threadID;
	/*
	////////////////////////////////////////////////////////
	//The following are properties of newly generated thread
	///////////////////////////////////////////////////////
	AddrSpace* addressSpace;  //The address space which this new thread will belong to
	Thread *t;                //a pointer to the new thread
	Thread *parentThread;     //a pointer to the parent thread
	int stackStart;           //a pointer to start point of a dedicated stack for new thread
	/////////////////////////////////////////////////////////

	//create the new thread
	t = new Thread(name);
	//int threadStackReg = (numPages - (UserStackSize/PageSize)*(MaxThreadsPerProcess - threadID - 1)) * PageSize - 16;
	//ASSERT(threadStackReg>-1);
	//machine->WriteRegister(StackReg, threadStackReg);
	t->Fork((VoidFunctionPtr)Kernel_Func,funcVAddr);
	DEBUG('x', "[KERNEL] Fork syscall Finished.\n");
	return threadID;*/
}

int Exec_Syscall(unsigned int nameVAddr, int len) {
	char* name;               //Expressive name of thread
	int processID;             //Identifier of the new threads. Unique per process
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Exec syscall started. size%i\n", currentThread->getProcessID(), currentThread->getThreadID(),len);
	//Generate a name for this thread
	name = new char[len+1];
	if (!name) {
		printf("%s","Can't allocate kernel buffer in Exec\n");
		return -1;
	}
	if( copyin(nameVAddr,len,name) == -1 ) {
		printf("%s","Bad pointer passed to Exec file name\n");
		delete[] name;
		return -1;
	}
	name[len]='\0';
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Exec file name size%i is %s\n",currentThread->getProcessID(), currentThread->getThreadID(),len, name);
	processID = processTable.Exec(name, false);
	delete []name;
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Exec syscall Finished.\n",currentThread->getProcessID(), currentThread->getThreadID());
	return processID;
}

void Exit_Syscall(int status) {
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Exit syscall.\n",currentThread->getProcessID(), currentThread->getThreadID());
	ProcessEntry* currentProcess = processTable.getProcess(currentThread->getProcessID());
	if(currentProcess == NULL){
		printf("%s","Could not find the current process in process table\n");
	}
	else
	{
		currentProcess->Exit(currentThread->getThreadID());
	}
	DEBUG('p', "[KERNEL PID#%i, TID#%i] Exit syscall finished.\n",currentThread->getProcessID(), currentThread->getThreadID());
	//currentThread->Finish();
}

void Yield_Syscall() {
    currentThread->Yield();
}

int CreateLock_Syscall(unsigned int vaddr, int length) {

    // acquire the name of the lock from the address passed in
    char *buf = new char[length+1];    // Kernel buffer to put the name in
    if (!buf) {
        // the character array was unable to be created, that's bad.
        return -1;
    }

    // copy the characters into the buffer we want to use as the name
    if( copyin(vaddr, length, buf) == -1 ) {
        // alert the user
        printf("%s","Bad pointer passed to Create Lock\n");
        // delete the array and return
        delete buf;
        return -1;
    }

    // add the terminating character to the end of the array so that it doesn't
    // float out into other memory looking for the end of the character array
    buf[length]='\0';

    // try and add the lock information to our global set of locks
    int ableToCreateLock = _kernelLocks.CreateLock(buf, currentThread->space);
    if(ableToCreateLock != -1) {
        return ableToCreateLock;
    } else {
        printf("Unable to create a new Kernel Lock\n");
    }

    return -1;
}

int CreateCV_Syscall(unsigned int vaddr, int length) {
    char *buf = new char[length+1];    // Kernel buffer to put the name in
    if (!buf) {
        // the character array was unable to be created, that's bad.
        return -1;
    }

    // copy the characters into the buffer we want to use as the name
    if( copyin(vaddr, length, buf) == -1 ) {
        // alert the user
        printf("%s","Bad pointer passed to Create CV\n");
        // delete the array and return
        delete buf;
        return -1;
    }

    // add the terminating character to the end of the array so that it doesn't
    // float out into other memory looking for the end of the character array
    buf[length]='\0';

    // try and add the lock information to our global set of locks
    int ableToCreateCV = _kernelLocks.CreateCV(buf, currentThread->space);
    if(ableToCreateCV != -1) {
        return ableToCreateCV;
    } else {
        printf("Unable to create a new Kernel Lock\n");
    }

    return -1;
}

void Acquire_Syscall(int lockId) {
    _kernelLocks.Acquire(lockId, currentThread->space);
}

void Release_Syscall(int lockId) {
    _kernelLocks.Release(lockId, currentThread->space);
}

void Wait_Syscall(int cvid, int lockId) {
    _kernelLocks.Wait(cvid, lockId, currentThread->space);
}

void Signal_Syscall(int cvid, int lockId) {
    _kernelLocks.Signal(cvid, lockId, currentThread->space);
}

void Broadcast_Syscall(int cvid, int lockId) {
    _kernelLocks.Broadcast(cvid, lockId, currentThread->space);
}

void DestroyLock_Syscall(int lockId) {
    _kernelLocks.DestroyLock(lockId, currentThread->space);
}

void DestroyCV_Syscall(int cvid) {
    _kernelLocks.DestroyCV(cvid, currentThread->space);
}

int Rand_Syscall(int max) {
    return rand()%max + 1;
}

//#endif


void ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2); // Which syscall?
    int rv=0; 	// the return value from a syscall

    if ( which == SyscallException ) {
	switch (type) {
	    default:
	        DEBUG('a', "Unknown syscall - shutting down.\n");
	    case SC_Halt:
            DEBUG('a', "Shutdown, initiated by user program.\n");
            interrupt->Halt();
            break;
	    case SC_Create:
            DEBUG('a', "Create syscall.\n");
            Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
	    case SC_Open:
            DEBUG('a', "Open syscall.\n");
            rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
	    case SC_Write:
            DEBUG('a', "Write syscall.\n");
            Write_Syscall(machine->ReadRegister(4),
                      machine->ReadRegister(5),
                      machine->ReadRegister(6));
            break;
	    case SC_Read:
            DEBUG('a', "Read syscall.\n");
            rv = Read_Syscall(machine->ReadRegister(4),
                      machine->ReadRegister(5),
                      machine->ReadRegister(6));
            break;
	    case SC_Close:
            DEBUG('a', "Close syscall.\n");
            Close_Syscall(machine->ReadRegister(4));
            break;
//#ifdef CHANGED
        case SC_Fork:
            DEBUG('a', "Fork syscall\n");
            rv  = Fork_Syscall(machine->ReadRegister(4), //Name
            		  	       machine->ReadRegister(5), //Length of name
            			       machine->ReadRegister(6));//Func virtual address
            break;
        case SC_Exec:
            DEBUG('a', "Exec syscall.\n");
            rv  = Exec_Syscall(machine->ReadRegister(4), //Name of file
                        		  	       machine->ReadRegister(5)); //Length of name
            break;
        case SC_Exit:
            DEBUG('a', "Exit syscall.\n");
            Exit_Syscall(machine->ReadRegister(4));
            break;
        case SC_Yield:
            DEBUG('a', "Yield syscall.\n");
            Yield_Syscall();
            break;
        case SC_CreateLock:
            DEBUG('a', "Create Lock syscall.\n");
            rv = CreateLock_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
        case SC_CreateCV:
            DEBUG('a', "Create CV syscall.\n");
            rv = CreateCV_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
        case SC_Acquire:
            DEBUG('a', "Acquire syscall.\n");
            Acquire_Syscall(machine->ReadRegister(4));
            break;
        case SC_Release:
            DEBUG('a', "Release syscall.\n");
            Release_Syscall(machine->ReadRegister(4));
            break;
        case SC_Wait:
            DEBUG('a', "Wait syscall.\n");
            Wait_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
        case SC_Signal:
            DEBUG('a', "Signal syscall.\n");
            Signal_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
        case SC_Broadcast:
            DEBUG('a', "Broadcast syscall.\n");
            Broadcast_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
            break;
        case SC_DestroyLock:
            DEBUG('a', "Destroy lock syscall.\n");
            DestroyLock_Syscall(machine->ReadRegister(4));
            break;
        case SC_DestroyCV:
            DEBUG('a', "Destroy CV syscall.\n");
            DestroyCV_Syscall(machine->ReadRegister(4));
            break;
        case SC_Rand:
            DEBUG('a', "Rand syscall.\n");
            rv = Rand_Syscall(machine->ReadRegister(4));
            break;
//#endif
	}

	// Put in the return value and increment the PC
	machine->WriteRegister(2,rv);
	machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg));
	machine->WriteRegister(PCReg,machine->ReadRegister(NextPCReg));
	machine->WriteRegister(NextPCReg,machine->ReadRegister(PCReg)+4);
	return;
    } else {
      cout<<"PID#" <<currentThread->getProcessID() <<" Thread#"<< currentThread->getThreadID() <<": Unexpected user mode exception - which:"<<which<<"  type:"<< type<<endl;
      interrupt->Halt();
    }
}
